//
//  BGOAnalyticsManager.m
//  Bango
//

#import "BGOAnalyticsManager.h"
#import "BGOProfile.h"
#import "BGOEvent.h"
#import "BGOEventParameter.h"
#import "BGOAnalyticsConstants.h"
#import "BGOSessionEventsConstants.h"
#import "BGOHelperUtils.h"
#import "BGOReachability.h"
#import "BGOFieldNameConstants.h"
#import "BGOTransmissionModeConstants.h"
#import "BGOPersistenceManager.h"
#import "JSON.h"
#import "UIDevice+Platform.h"
#import "UIDevice+MacAddress.h"
#import "NSString+Sha1Hash.h"
#import "BGOAnalyticsManagerDelegate.h"
#import "BGOProfilePersistent.h"

// Custom Logger for BangoAnalyticsManager
#define BAMLog(fmt,...) [self debug:__FILE__ lineNumber:__LINE__ format:(fmt),##__VA_ARGS__]

static BGOAnalyticsManager *sharedMyManager = nil;

#define PERSISTENCE [BGOPersistenceManager sharedPersistenceManager]

@interface BGOAnalyticsManager (PRIVATE) 
// for useragent stuff
-(NSString *)userAgentString;

// kicks off process to transmit all outstanding analytics data to bango
-(void)transmitAll;
// transmits analytics data to bango for specific profile
-(void)transmitProfile:(BGOProfile*)profile;
// handles the response back from bango after transmission of data
-(void)handleTransmissionForResponse:(NSString*)response;

//retrieve Bango User ID
-(void) GetBangoUserId:(NSString*)applicationId;
// logs to debug
-(void)debug:(char*)file 
  lineNumber:(int)lineNumber 
      format:(NSString *)format, ... ;

-(NSString *)connectionType;

@end

@implementation BGOAnalyticsManager

// fields
@synthesize profileFieldsDictionary, eventFieldsDictionary;

// config options
@synthesize applicationChannel=_applicationChannel, 
applicationType=_applicationType,
applicationVersion=_applicationVersion, 
continueSessionSeconds=_continueSessionSeconds,
findLocation=_findLocation,
automaticDataTransmission=_automaticDataTransmission,
postUrl=_postUrl,
useIdentifierForVendor=_useIdentifierForVendor,
identifyBangoUser=_identifyBangoUser,
userId=_userId,
bangoUserId=_bangoUserId,
ipAddress=_ipAddress;
// device can make mobile phone calls
@synthesize deviceIsMobile=_deviceIsMobile;
// useragent
@synthesize userAgent=_userAgent;
@synthesize xUserAgent=_xUserAgent;
// core location
@synthesize locationManager=_locationManager;

// transmission
@synthesize transmitting=_transmitting, 
transmissionMode=_transmissionMode,
transmittingProfile=_transmittingProfile, 
responseData=_responseData,
transmissionStartDate=_transmissionStartDate;

// debug log
@dynamic debugLog;
@dynamic postChunkSize;

@synthesize delegate=_delegate;

// logging level
@synthesize loggingLevel    = _loggingLevel;
@synthesize logEnabled  = _loggingEnabled;
@synthesize eventThrottling = _eventThrottling;

#pragma mark -
#pragma mark Memory Management

// dealloc
-(void) dealloc
{
	BAMLog(@"> %s", __PRETTY_FUNCTION__);	
	[_locationManager release];
    [_delegate release];
	[super dealloc];
}

#pragma mark Singleton Methods
+ (BGOAnalyticsManager *)sharedManager {
	@synchronized(self) {
		if(sharedMyManager == nil) {
			sharedMyManager = [[super allocWithZone:NULL] init];
		}
	}
	return sharedMyManager;
}

+ (id)allocWithZone:(NSZone *)zone {
	return [[self sharedManager] retain];
}

- (id)copyWithZone:(NSZone *)zone {
	return self;
}

- (id)retain {
	return self;
}

- (unsigned)retainCount {
	return UINT_MAX; //denotes an object that cannot be released
}

- (oneway void)release {
	// never release
}

- (id)autorelease {
	return self;
}

// init method
- (id) init {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	if(self = [super init]) 
	{
		//_userAgent = [self userAgentString];
        _userAgent = [[[UIDevice currentDevice] userAgentString] retain];
        _xUserAgent = [[[UIDevice currentDevice] userAgentString] retain];
		
		// Set continue session seconds to -1, if the number is 0 or greater than we will store it in the profile
		self.continueSessionSeconds = -1;
		
		// Set default for automatic data transmission to YES
		self.automaticDataTransmission = YES;
        
        // By default, we don't throttle events
        _eventThrottling = NO;
        
        _useIdentifierForVendor = NO;
        // Default enable logging of events
        
        _loggingEnabled = YES;
        _identifyBangoUser = NO;
	}
    
	return self;
}

#pragma mark -
#pragma mark Bango Analytics API Methods

/*
 * Session Methods: These are to do with the Session Profile. i.e. starting a session (creating a new profile) and ending a session (attempting to transmit
 * the profile info).
 */

// This method initiates logging, this must be called after the configuration methods but before any other logging or any other session methods.
-(void)onStartSessionWithApplicationId :(NSString*)applicationId {
	[self onStartSessionWithApplicationId :applicationId 
                           eventParameters:nil
                               eventDetail:nil
                                eventValue:nil];
}

// This method initiates logging, this must be called after the configuration methods but before any other logging or any other session methods.
-(void)onStartSessionWithApplicationId:(NSString*)applicationId
                       eventParameters:(NSDictionary*)eventParameters
                           eventDetail:(NSString*)eventDetail
                            eventValue:(NSString*)eventValue {
	BAMLog(@".");
	BAMLog(@".");
	BAMLog(@".");
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	BAMLog(@"session started with applicationId: %@", applicationId);
     NSString *bangoUserId = self.bangoUserId;
    
	if(!self.isLoggingEnabled) {
		BAMLog(@"logging has been disabled - not creating profile");
		return;
	}
	
	// start core location if required and if it is not already started
	if(_findLocation == YES) {
		BAMLog(@"onStartSession with findLocation == YES");
		if(self.locationManager == nil) {
			BAMLog(@"initialising locationManager");
			self.locationManager = [[[CLLocationManager alloc] init] autorelease]; 
			self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; 
			self.locationManager.delegate = self;
			[self.locationManager startUpdatingLocation];
		} else {
			BAMLog(@"locationManager already initialised");
		}
	} else {
		BAMLog(@"onStartSesion with findLocation == NO");
	}
	
    // conection type
	NSString *connectionType = [self connectionType];
	BAMLog(@"connectionType: %@", connectionType);
	
	// location
	CLLocation *curPos = self.locationManager.location; 
	double latitude = curPos.coordinate.latitude;
	double longitude = curPos.coordinate.longitude;
	double altitude = curPos.altitude;
	
	// todo should we take average of horizontal and vertical accuracy?
	double accuracy = curPos.horizontalAccuracy;
	
    double locationLatitude = 0.0, locationLongitude = 0.0,
    locationAccuracy = 0.0, locationAltitude = 0.0;
    
	if(self.findLocation) {
		BAMLog(@"setting location");
		// todo the location will probably not have been picked up this quickly anyway so we need a plan to set it once its kicked in
        locationLatitude = [BGOHelperUtils getRoundedLocation:latitude];
        locationLongitude = [BGOHelperUtils getRoundedLocation:longitude];
        locationAccuracy = [BGOHelperUtils getRoundedLocation:accuracy];
        locationAltitude = [BGOHelperUtils getRoundedLocation:altitude];
	} else {
		BAMLog(@"not checking location");
	}
    
    NSString *userId = self.userId;
   
       
    if (userId == nil) {
                        
        if ([[UIDevice currentDevice] respondsToSelector:@selector(identifierForVendor)] && (self.useIdentifierForVendor == YES || [BGOHelperUtils iOSMeetsOrExceedsTargetVersion:BGO_iOS_MAX_VERSION] )) {
            
            
            userId = [[[[UIDevice currentDevice] identifierForVendor] UUIDString] sha1];
            
        }
        else{
            userId = [[UIDevice currentDevice].macAddress sha1];
        }
        
    }
    if(bangoUserId == nil && _identifyBangoUser == YES){
        [self GetBangoUserId:applicationId];
    }
    
    [PERSISTENCE logProfileWithSdkType:BGO_SDK_TYPE
                            sdkVersion:BGO_SDK_VERSION
                         applicationId:applicationId
                       applicationType:self.applicationType
                    applicationChannel:self.applicationChannel
                    applicationVersion:self.applicationVersion
                                userId:userId
                    sessionRestartTime:self.continueSessionSeconds
                               created:[NSDate date]
                        connectionType:connectionType
                             isRoaming:NO
                        locationSource:nil
                      locationAltitude:locationAltitude
                      locationLatitude:locationLatitude
                     locationLongitude:locationLongitude
                      locationAccuracy:locationAccuracy
                           netOperator:nil
                          operatorName:nil
                    operatorCountryIso:nil
                           simOperator:nil
                                 brand:@"Apple"
                                device:nil
                             ipAddress:self.ipAddress
                                 model:[UIDevice currentDevice].model
                               product:nil
                               version:[UIDevice currentDevice].platform
                               buildId:nil
                operatorConnectionType:nil
                           bangoUserId:bangoUserId];
    
    
	// Step 2 Create the event
    [PERSISTENCE logEventWithConnectionType:connectionType
                     operatorConnectionType:0
                                 errorLevel:0
                                  eventName:SESSION_START
                                eventDetail:eventDetail
                                 eventValue:eventValue
                                    errorId:0
                                  eventTime:[NSDate date]
                            eventParameters:eventParameters];
	
	// transmit all except for latest created
	if(self.automaticDataTransmission == YES) {
		BAMLog(@"automaticDataTransmission enabled. Transmitting all excluding latest.");
		[self commenceTransmissionWithMode:BGO_TRANSMIT_EXCLUDE_LATEST];
	}
}

// method which creates an event for the current profile
// can be used to create any kind of event
-(void)onEvent:(NSString *)eventName eventDetail:(NSString *)eventDetail eventValue:(NSString*)eventValue eventParameters:(NSDictionary*)eventParameters {
    BAMLog(@"> %s", __PRETTY_FUNCTION__);
    
    
    if(!_loggingEnabled) {
        BAMLog(@"logging has been disabled - not creating event");
        return;
    }
    
    
    // determine if we should use event throttling or not
    if (!_eventThrottling) {
        [PERSISTENCE logEventWithConnectionType:[self connectionType]
                         operatorConnectionType:0
                                     errorLevel:0
                                      eventName:eventName
                                    eventDetail:eventDetail
                                     eventValue:eventValue
                                        errorId:nil
                                      eventTime:[NSDate date]
                                eventParameters:eventParameters];
    } else if ([self shouldLogEvent:eventName]                ||
               [eventName isEqualToString:SESSION_END]        ||
               [eventName isEqualToString:SESSION_IDLE_END]   ||
               [eventName isEqualToString:SESSION_IDLE_START] ||
               [eventName isEqualToString:SESSION_START]) {
        [PERSISTENCE logEventWithConnectionType:[self connectionType]
                         operatorConnectionType:0
                                     errorLevel:0
                                      eventName:eventName
                                    eventDetail:eventDetail
                                     eventValue:eventValue
                                        errorId:nil
                                      eventTime:[NSDate date]
                                eventParameters:eventParameters];
    } else {
        // no EventThrottling condition has been satisfied so we don't log events.
        BAMLog(@"EventThrottling Enabled - %@ event logging not in events list so not logging event. ",eventName);
        
    }
}

// This method indicates that the session has ended and will initiate the transmitting of data.
-(void)onEndSession {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onEndSessionWithEventParameters:nil eventDetail:nil eventValue:nil];
}

// This method indicates that the session has ended and will initiate the transmitting of data.
-(void)onEndSessionWithEventParameters:(NSDictionary*)eventParameters
                           eventDetail:(NSString*)eventDetail
                            eventValue:(NSString*)eventValue {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
    [self onEvent:SESSION_END
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:eventParameters];
}

// This method indicates that the session has entered an idle state, for example the application has lost focus.
-(void)onIdle {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onIdleWithEventParameters:nil eventDetail:nil eventValue:nil];
}

// This method indicates that the session has entered an idle state, for example the application has lost focus.
-(void)onIdleWithEventParameters:(NSDictionary*)eventParameters
                     eventDetail:(NSString*)eventDetail
                      eventValue:(NSString*)eventValue {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	[self onEvent:SESSION_IDLE_START 
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:eventParameters];
    
    // transmit all except for latest created
    /*if(self.automaticDataTransmission == YES) {
        BAMLog(@"automaticDataTransmission enabled. Transmitting all inlcuding profiles but not deleting the latest on completion");
        [self commenceTransmissionWithMode:BGO_TRANSMIT_ALL_RETAIN_LATEST];
    }*/
}

// This method indicates that the session has entered an idle state, for example the application has lost focus.
-(void)onIdleWithEventParameters:(NSDictionary*)eventParameters
                     eventDetail:(NSString*)eventDetail
                      eventValue:(NSString*)eventValue
                  fromBackground:(BOOL)fromBackground {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
    
    
	[self onEvent:SESSION_IDLE_START 
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:eventParameters];
    
    // transmit all except for latest created
    /*if(self.automaticDataTransmission == YES) {
        BAMLog(@"automaticDataTransmission enabled. Transmitting all inlcuding profiles but not deleting the latest on completion");
        if(fromBackground == NO) {
            BAMLog(@"using transmit all retain latest in Standard mode");
            [self commenceTransmissionWithMode:BGO_TRANSMIT_ALL_RETAIN_LATEST];
        } else {
            BAMLog(@"using transmit all retain latest in BG mode (only use when app is going into idle state)");
            [self commenceTransmissionWithMode:BGO_TRANSMIT_ALL_RETAIN_LATEST_BG];
            
        }
    }*/
}

// This method indicates that the session has resumed after an idle state, for example the application has gained focus after losing focus.
-(void)onResume {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onResumeWithEventParameters :nil eventDetail:nil eventValue:nil];	
}	

// This method indicates that the session has resumed after an idle state, for example the application has gained focus after losing focus.
-(void)onResumeWithEventParameters:(NSDictionary*)eventParameters
                       eventDetail:(NSString*)eventDetail
                        eventValue:(NSString*)eventValue {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	[self onEvent:SESSION_IDLE_END 
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:eventParameters];
	
	// transmit all profiles but do not delete last profile - delete all events in last profile.
	if(self.automaticDataTransmission == YES) {
		BAMLog(@"automaticDataTransmission enabled. Transmitting all data - and retaining latest profile");
		[self commenceTransmissionWithMode:BGO_TRANSMIT_ALL_RETAIN_LATEST];	
	}
}

/*
 * Logging Methods: These are to do with logging events such as video start and stop
 */

// This method logs an onDemandVideoStart event.
-(void)onDemandVideoStart {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onDemandVideoStartWithEventParameters:nil
                                    eventDetail:nil
                                     eventValue:nil];
}

// This method logs an onDemandVideoStart event.
-(void)onDemandVideoStartWithEventParameters:(NSDictionary*)eventParameters
                                 eventDetail:(NSString*)eventDetail
                                  eventValue:(NSString*)eventValue {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	NSMutableDictionary *mutable;
	if(eventParameters == nil) {
		mutable = [[NSMutableDictionary alloc] init];
	} else {
		mutable = [[NSMutableDictionary alloc] initWithDictionary:eventParameters];
	}
	
	[mutable setObject:BGO_VIDEO_ONDEMANDPARAMETER 
                forKey:BGO_VIDEO_PARAMETERNAME];
	
	[self onEvent:EVENT_VIDEOSTART 
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:mutable];
	
	[mutable release];
}

// This method logs an onDemandVideoStop event.
-(void)onDemandVideoStop {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	[self onDemandVideoStopWithEventParameters:nil
                                   eventDetail:nil
                                    eventValue:nil];
}

// This method logs an onDemandVideoStop event.
-(void)onDemandVideoStopWithEventParameters:(NSDictionary*)eventParameters
                                eventDetail:(NSString*)eventDetail
                                 eventValue:(NSString*)eventValue {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	
	NSMutableDictionary *mutable;
	if(eventParameters == nil) {
		mutable = [[NSMutableDictionary alloc] init];
	} else {
		mutable = [[NSMutableDictionary alloc] initWithDictionary:eventParameters];
	}
	
	[mutable setObject:BGO_VIDEO_ONDEMANDPARAMETER 
                forKey:BGO_VIDEO_PARAMETERNAME];
	
	[self onEvent:EVENT_VIDEOSTOP
      eventDetail:eventDetail
       eventValue:eventValue 
  eventParameters:mutable];
	
	[mutable release];
	
}

// This method logs an onLiveVideoStart event.
-(void)onLiveVideoStart {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onLiveVideoStartWithEventParameters :nil eventDetail:nil eventValue:nil];
}

// This method logs an onLiveVideoStart event.
-(void)onLiveVideoStartWithEventParameters :(NSDictionary*)eventParameters eventDetail:(NSString*)eventDetail eventValue:(NSString*)eventValue {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	NSMutableDictionary *mutable;
	if(eventParameters == nil) {
		mutable = [[NSMutableDictionary alloc] init];
	} else {
		mutable = [[NSMutableDictionary alloc] initWithDictionary:eventParameters];
	}
	
	[mutable setObject:BGO_VIDEO_LIVEPARAMETER forKey:BGO_VIDEO_PARAMETERNAME];
	
	[self onEvent:EVENT_VIDEOSTART
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:mutable];
	
	[mutable release];
}

// This method logs an onLiveVideoStop event.
-(void)onLiveVideoStop {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	[self onLiveVideoStopWithEventParameters :nil eventDetail:nil eventValue:nil];
}

// This method logs an onLiveVideoStop event.
-(void)onLiveVideoStopWithEventParameters:(NSDictionary*)eventParameters
                              eventDetail:(NSString*)eventDetail
                               eventValue:(NSString*)eventValue {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	
	NSMutableDictionary *mutable;
	if(eventParameters == nil) {
		mutable = [[NSMutableDictionary alloc] init];
	} else {
		mutable = [[NSMutableDictionary alloc] initWithDictionary:eventParameters];
	}
	
	[mutable setObject:BGO_VIDEO_LIVEPARAMETER forKey:BGO_VIDEO_PARAMETERNAME];
	
	[self onEvent:EVENT_VIDEOSTOP
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:mutable];
	
	[mutable release];
}

// This method logs an onPageView event.
-(void)onPageView {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onPageViewWithEventParameters :nil eventDetail:nil eventValue:nil];
}

// This method logs an onPageView event.
-(void)onPageViewWithEventParameters:(NSDictionary*)eventParameters
                         eventDetail:(NSString*)eventDetail
                          eventValue:(NSString*)eventValue {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onEvent:EVENT_PAGEVIEW
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:eventParameters];
}

// This method logs a search event.
-(void)onSearch {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onSearchWithEventParameters :nil eventDetail:nil eventValue:nil];
}

// This method logs a search event.
-(void)onSearchWithEventParameters :(NSDictionary*)eventParameters eventDetail:(NSString*)eventDetail eventValue:(NSString*)eventValue {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onEvent:EVENT_SEARCH 
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:eventParameters];
}

// This method logs an error event.
-(void)onError {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	[self onErrorWithEventParameters:nil
                             message:nil
                          eventClass:nil
                     eventErrorLevel:[NSNumber numberWithInt:BGO_ERROR_USER]
                        eventErrorId:nil];
}

// This method logs an error event.
-(void)onErrorWithEventParameters :(NSDictionary*)eventParameters message:(NSString*)message eventClass:(NSString*)eventClass eventErrorId:(NSString*)eventErrorId {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	[self onErrorWithEventParameters:eventParameters message:message eventClass:eventClass eventErrorId:eventErrorId];
}

// This method logs an error event.
-(void)onErrorWithEventParameters :(NSDictionary*)eventParameters message:(NSString*)message eventClass:(NSString*)eventClass 
                   eventErrorLevel:(NSNumber*)eventErrorLevel eventErrorId:(NSString*)eventErrorId {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	[PERSISTENCE logEventWithConnectionType:[self connectionType]
                     operatorConnectionType:0
                                 errorLevel:[eventErrorLevel intValue]
                                  eventName:EVENT_ERROR
                                eventDetail:message
                                 eventValue:eventClass
                                    errorId:eventErrorId
                                  eventTime:[NSDate date]
                            eventParameters:eventParameters];
}

#pragma mark Log Level

// Set the events for
-(void)setEvents:(NSArray *)arrayOfEvents forLogLevel:(int)loggingLevel
{
    if (!_loggingEvents)
    {
        _loggingEvents = [[NSMutableDictionary alloc] init];
    }

    // Can't set events for logging level 0
    if (loggingLevel == 0)
    {
        return;
    }
    
    // Can't set an empty definition
    if (!arrayOfEvents)
    {
        return;
    }
    
    [_loggingEvents setObject:arrayOfEvents forKey:[NSString stringWithFormat:@"%d",loggingLevel]];
}

-(void)setLoggingLevel:(int)loggingLevel
{
    _loggingLevel = loggingLevel;
    
    if (_loggingEvents)
    {
        NSArray *arrayOfEvents = [_loggingEvents objectForKey:[NSString stringWithFormat:@"%d",loggingLevel]];
        if (arrayOfEvents)
        {
            if ([arrayOfEvents count])
            {
                //_loggingLevel = loggingLevel;
                
                BOOL logAllEvents = (//[self shouldLogEvent:SESSION_START]      &&
                                     //[self shouldLogEvent:SESSION_END]        &&
                                     //[self shouldLogEvent:SESSION_IDLE_END]   &&
                                     //[self shouldLogEvent:SESSION_IDLE_START] &&
                                     [self shouldLogEvent:EVENT_ERROR]        &&
                                     [self shouldLogEvent:EVENT_PAGEVIEW]     &&
                                     [self shouldLogEvent:EVENT_SEARCH]       &&
                                     [self shouldLogEvent:EVENT_VIDEOSTART]   &&
                                     [self shouldLogEvent:EVENT_VIDEOSTOP]    &&
                                     [self shouldLogEvent:EVENT_INTIALIZE]    &&
                                     [self shouldLogEvent:EVENT_UNINTIALIZE]  &&
                                     [self shouldLogEvent:EVENT_AUDIOSTART]   &&
                                     [self shouldLogEvent:EVENT_AUDIOSTOP]     );

                if (_loggingLevel == 0)
                    logAllEvents = YES;
                
                //_eventThrottling = !logAllEvents;
            }
        }
    }
}

// Returns true if we should log a given event
-(BOOL)shouldLogEvent:(NSString*)eventName
{
    if (_loggingLevel == 0)
        return YES;
    
    if (_loggingEvents)
    {
        for( NSString *keyString in [_loggingEvents allKeys] )
        {
            int keyValue = [keyString intValue];
            
            if ((_loggingLevel & keyValue) == keyValue)
            {
                NSArray *arrayOfEvents = [_loggingEvents objectForKey:keyString];
                
                if (arrayOfEvents)
                {
                    for (NSString* candidate in arrayOfEvents)
                    {
                        if ([candidate isEqualToString:eventName])
                            return YES;
                    }
                }
            }
        }
    }

    return NO;
}

#pragma mark Application Context

-(void)initialize:(NSString*)applicationId
  eventParameters:(NSDictionary*)eventParameters
      eventDetail:(NSString*)eventDetail
       eventValue:(NSString*)eventValue
{
    
    BAMLog(@"> %s", __PRETTY_FUNCTION__);
    
	[self onEvent:EVENT_INTIALIZE 
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:eventParameters];
}

-(void)uninitialize:(NSString*)applicationId
    eventParameters:(NSDictionary*)eventParameters
        eventDetail:(NSString*)eventDetail
         eventValue:(NSString*)eventValue
{
    
    BAMLog(@"> %s", __PRETTY_FUNCTION__);
    
	[self onEvent:EVENT_UNINTIALIZE 
      eventDetail:eventDetail
       eventValue:eventValue
  eventParameters:eventParameters];
	
}

#pragma mark -
#pragma mark UserAgent Detection Methods

/////////////////////////////////////////
// for getting the useragent - adapted from handy method by Björn Sållarp
// http://blog.sallarp.com/iphone-ipad-get-user-agent-for-uiwebview/#more-1003
//
/*-(NSString*)userAgentString
{
	BAMLog(@"> %s", __PRETTY_FUNCTION__);

	// We should create web view on the main thread ; however we've protected against this elsewhere in the App Delegate now,
	// so we shouldn't need to do the forced selection of the main thread here now.
	
	//dispatch_async(dispatch_get_main_queue(), ^{
		_webView = [[UIWebView alloc] init];
		_webView.delegate = self;
		[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.bango.com"]]];
		// Wait for the web view to load our bogus request and give us the secret user agent.
		while (self.userAgent == nil) 
		{
			// This executes another run loop. 
			[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
		}
	//});	
	
	return self.userAgent;
}

-(BOOL)webView:(UIWebView *)webView 
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	self.userAgent = [request valueForHTTPHeaderField:@"User-Agent"];
	
	BAMLog(@"setting userAgent: %@", self.userAgent);
	
	// Return no, we don't care about executing an actual request.
	return NO;
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	self.userAgent = @"";
	BAMLog(@"setting userAgent to empty string due to error : %@", [error description]);
}*/

#pragma mark -
#pragma mark AnalyticsTransmission Methods

// commense transmission with the specified mode
-(void)commenceTransmissionWithMode:(NSString*)mode {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	BAMLog(@"previous mode: %@", _transmissionMode);
	BAMLog(@"new mode: %@", mode);
	self.transmissionMode = mode;
	
	// call transmit All
    [self transmitAll];
}


// kicks off process to transmit all outstanding analytics data to bango
-(void)transmitAll{
 	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	BAMLog(@"transmissionMode: %@", _transmissionMode);
	
	// don't attempt to transmit if we are already transmitting
	if(_transmitting) {
        
        // first check to see that the latest transmission occurred within the timeout time and if so resolve the deadlock.
        NSTimeInterval elapsedTime = - [_transmissionStartDate timeIntervalSinceNow];
        
        BAMLog(@"elapsedTime %f", elapsedTime);
        
        if(elapsedTime >  BGO_TRANSMIT_TIMEOUT + 1) {
            BAMLog(@"Last transmission was started longer ago than timeout time. Resetting transmission transmissionStartDate: %@ timeNow: %@ elapsedTime: %f",
                   _transmissionStartDate, [NSDate date], elapsedTime);
            _transmitting = NO;
        } else {
            BAMLog(@"already transmitting - attempt made to transmit whilst already transmitting - aborting transmission");
            return;
        }
	}
	
    BOOL excludeCurrentProfile = [BGO_TRANSMIT_EXCLUDE_LATEST isEqualToString:_transmissionMode];
    
 	BAMLog(@"Excluding current profile: %@", excludeCurrentProfile ? @"YES" : @"NO");
    
	NSArray *profiles = [PERSISTENCE getProfilesForPost:excludeCurrentProfile chunked:YES bangoUserId:self.bangoUserId];
	
	int totalProfiles = (int)[profiles count];
	
	BAMLog(@"totalProfiles: %i", totalProfiles);
	
	if(totalProfiles >= 1) {
        BAMLog(@"transmitting profile");
        [self transmitProfile:[profiles objectAtIndex:0]];
	} else {
		BAMLog(@"no profiles to transmit - terminating profile transmission");
		self.transmissionMode = nil;
	}
}

// transmits analytics data to bango for specific profile
-(void)transmitProfile:(BGOProfile*)profile {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	BAMLog(@"profile to transmit created: %@", profile.created);
	BAMLog(@"profile to transmit: %@", profile);
	
    
    
    
	if(profile.events == nil || profile.events.count == 0) {
        
        
        
		// Check if Profile contains events - if it does not contain events then we should not transmit it
		// We can be left with an eventless profile immediately after performing a transmit all retain latest.
		// In addition, if a new profile has been created after we have been left with an eventless profile
		// (e.g. due to app crash before new events being added) then we can delete such a profile.
		// We just need to ensure that there is a current profile to place events in (i.e. this is not the
		// current profile).
        
		BAMLog(@"Attempting to transmit profile with no events");
		BAMLog(@"Skipping Profile Transmission");
        
        // mark profile as posted
        [PERSISTENCE updateEventsPosted:profile.id];
		// then delete it
        [PERSISTENCE deleteEventsPosted:profile.id];
		
		return;
	}
    //    BOOL islogeEvent = [self isEventNeedToLog:profile];
    //	    if (islogeEvent==NO) {
    //        BAMLog(@"Attempting to transmit profile with no events");
    //        BAMLog(@"Skipping Profile Transmission");
    //        
    //        // mark profile as posted
    //        [PERSISTENCE updateEventsPosted:profile.id];
    //        // then delete it
    //        [PERSISTENCE deleteEventsPosted:profile.id];
    //        
    //        return;
    //    }
	// Set the profile we are transmitting
	self.transmittingProfile = profile;
    
    [PERSISTENCE updateEventsPostInProgress:profile];
	
    //	SBJsonWriter *writer = [SBJsonWriter new];
    //	writer.sortKeys = YES;
    //	
    //	// add spacing / tabs etc - best not to do this in production
    //	writer.humanReadable = YES;
    //	
    //	NSError *error = nil;
    //  
    //	// create JSON String of the profile and the events and their event params
    //	NSString *profileJson = [writer stringWithObject:profile error:&error];
    //	BAMLog(@"profileJson %@", profileJson);
    //  
    //	[writer release];
    //	
    //	BAMLog(@"json errors: %@", error);
	
    NSString *profileJson = [[profile toJson] JSONRepresentation];
    BAMLog(@"profileJson %@", profileJson);
    
	NSString *urlString;
	if(self.postUrl != nil) {
		BAMLog(@"postUrl overriden in userConfig");
		urlString = self.postUrl;
	} else {
		BAMLog(@"using default postUrl", BGO_SETTING_POST_URL);
		urlString = BGO_SETTING_POST_URL;
	}
	
	BAMLog(@"transmission URL: %@", urlString);
	
	NSURL *url = [NSURL URLWithString: urlString];
    
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url 
                                                       cachePolicy:NSURLRequestReloadIgnoringCacheData 
                                                   timeoutInterval:BGO_TRANSMIT_TIMEOUT];
	[req setValue:@"application/json" forHTTPHeaderField: @"Content-Type"];
	[req setValue:_userAgent forHTTPHeaderField:@"User-Agent"];
	[req setValue:_xUserAgent forHTTPHeaderField:@"X-Bango-User-Agent"];
	[req setHTTPMethod:@"POST"];
	[req setHTTPBody:[profileJson dataUsingEncoding:NSUTF8StringEncoding]];
    
    BAMLog(@"transmissionMode: %@", _transmissionMode);
    
    // mark that we're transmitting
    _transmitting = YES;
    
    // note the time
    self.transmissionStartDate = [NSDate date];
    
    if ([BGO_TRANSMIT_ALL_RETAIN_LATEST_BG isEqualToString:_transmissionMode]) {
        NSURLResponse *response = NULL;
        NSError *error = NULL;
        
        NSData *returnData = [NSURLConnection sendSynchronousRequest:req 
                                                   returningResponse:&response
                                                               error:&error];
        
        if (error == nil && response != nil) {
            // now 	 the response
            NSString *responseBody = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
            [self handleTransmissionForResponse:responseBody]; 
            [responseBody release];
        } else {
            BAMLog(@"error %@ occured during synchronous transmission for idle state whilst for profile : %@", error, self.transmittingProfile);
            
            [PERSISTENCE updatePostedStatusOnTransmissionFail:profile.id];
            self.transmittingProfile = nil;
            _transmitting = NO;
            
            BAMLog(@"transmittingProfile data has been reset - aborting all transmissions");	
            
            if (_delegate != nil && [_delegate respondsToSelector:@selector(transmitFailure)]) {
                [_delegate performSelector:@selector(transmitFailure)];
            }
        }
    } else {
        // start the async request
        [NSURLConnection connectionWithRequest:req delegate:self];
    }
}

-(void)GetBangoUserId:(NSString *)applicationId{
    NSString *bangoUserId = nil;
    if (_identifyBangoUser == YES) {
        BGOProfilePersistent *profilePersistent = [PERSISTENCE GetProfilePersistent];
        bangoUserId = profilePersistent.BangoUserId;
        BAMLog(@"Bango User Id: %@", bangoUserId);
        
        if (bangoUserId == nil || [profilePersistent.Date timeIntervalSinceNow] < 0) {
            NSString *url = [NSString stringWithFormat:@"%@%@", BGO_SETTING_IDENTIFIER_URL, applicationId];
            BAMLog(@"Identifier URL: %@", url);
            NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
            //[NSURLConnection connectionWithRequest:request delegate:self tag:2];
            NSOperationQueue *queue = [[NSOperationQueue alloc] init];
            [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
                BAMLog(@"Data: %d", data.length);
                if ([data length] > 0 && connectionError == nil) {
                    parser = [[NSXMLParser alloc] initWithData:data];
                    [parser setDelegate:self];
                    [parser parse];
                    errorParsing = NO;
                    //[PERSISTENCE LogProfilePersistent:self.bangoUserId];
                    BAMLog(@"%@", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
                }
                else if ([data length] == 0 && connectionError == nil){
                    BAMLog(@"Nothing was downloaded");
                }
                else if (connectionError != nil){
                    BAMLog(@"Error: %@", connectionError);
                }
            }];
        }
    }
}


// handles the response back from bango after transmission of data
-(void)handleTransmissionForResponse:(NSString*)response {
    BAMLog(@"> %s", __PRETTY_FUNCTION__);
    
    BAMLog(@"response: %@", response);
    BAMLog(@"transmittingProfile %@", self.transmittingProfile.created);
	
	// if request starts with OK then store that we have transmitted the data successfully
	// NB. The two responses indidcating success are OK and OK_WITH_ERRORS
	if([response hasPrefix:BGO_POST_RESPONSE_SUCCESS]) {
		BAMLog(@"Profile Transmission Successful - determining delete logic");
		
		BAMLog(@"transmissionMode %@", self.transmissionMode);
		
		if([self.transmissionMode isEqualToString:BGO_TRANSMIT_ALL] ||
           [self.transmissionMode isEqualToString:BGO_TRANSMIT_EXCLUDE_LATEST]) {
			// delete the profile which has just been transmitted as both transmission modes require full profile deletion
			BAMLog(@"deleting profile %@ ", self.transmittingProfile.created);
            
			// attempt to delete and then send next profile
			BOOL success = [PERSISTENCE updateEventsPosted:self.transmittingProfile.id] &&
            [PERSISTENCE deleteEventsPosted:self.transmittingProfile.id];
            
			if (success) {
				BAMLog(@"profile deleted succesfully - reseting transmittngData and continuing with further transmissions");
				// can kick off transmission
				self.transmittingProfile = nil;
				_transmitting = NO;
                
                if (_delegate != nil && [_delegate respondsToSelector:@selector(transmitSuccess)]) {
                    [_delegate performSelector:@selector(transmitSuccess)];
                }
                
				[self transmitAll];
			} else {
				BAMLog(@"problem deleting transmitting profile - reseting transmittngData but aborting further transmissions");
				self.transmittingProfile = nil;
				_transmitting = NO;
			}
			
		} else if([self.transmissionMode isEqualToString:BGO_TRANSMIT_ALL_RETAIN_LATEST] || [self.transmissionMode isEqualToString:BGO_TRANSMIT_ALL_RETAIN_LATEST_BG]) {
			// get the current profile and see if it is the latest or not
			// if it is not the latest we can delete
			// if it is the latest then we only want to delete the events
			
            BAMLog(@"profile is not latest - can delete");
            // attempt to delete and then send next profile
            BOOL success = [PERSISTENCE updateEventsPosted:self.transmittingProfile.id] &&
            [PERSISTENCE deleteEventsPosted:self.transmittingProfile.id];
            
            if (success) {
                BAMLog(@"profile deleted succesfully - reseting transmittngData and continuing with further transmissions");
                // can kick off transmission
                self.transmittingProfile = nil;
                _transmitting = NO;
                
                if (_delegate != nil && [_delegate respondsToSelector:@selector(transmitSuccess)]) {
                    [_delegate performSelector:@selector(transmitSuccess)];
                }
                
                [self transmitAll];
            } else {
                BAMLog(@"problem deleting transmitting profile - reseting transmittngData but aborting further transmissions");
                self.transmittingProfile = nil;
                _transmitting = NO;
            }
		} else {
			BAMLog(@"unexpected transmissionMode %@", self.transmissionMode);
			// log internal error
			_transmitting = NO;
            //[PERSISTENCE updatePostedStatusOnTransmissionFail:self.transmittingProfile.id];
		}
	} else {
		BAMLog(@"request was not posted successfully");
        [PERSISTENCE updatePostedStatusOnTransmissionFail:self.transmittingProfile.id];
		_transmitting = NO;
        if (_delegate != nil && [_delegate respondsToSelector:@selector(transmitFailure)]) {
            [_delegate performSelector:@selector(transmitFailure)];
        }
	}	
}

//
//  Received at the start of the response from the server.  This may get called multiple times in certain redirect scenerios.

// 
#pragma mark - NSURLConnection delegate methods

- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
	
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	
	if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
		NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
		int status = (int)[httpResponse statusCode];
        
   if (!((status >= 200) && (status < 300))) {
            BAMLog(@"Connection failed with status %@", status);
            [PERSISTENCE updatePostedStatusOnTransmissionFail:self.transmittingProfile.id];
            self.transmittingProfile = nil;
            _transmitting = NO;
            BAMLog(@"transmittingProfile data has been reset - aborting all transmissions");
            
            if (_delegate != nil && [_delegate respondsToSelector:@selector(transmitFailure)]) {
                [_delegate performSelector:@selector(transmitFailure)];
            }
        } else {
            // make the working space for the data buffer.
            self.responseData = [[[NSMutableData alloc] initWithCapacity:1024] autorelease];
        }
        
		
		
	}
}

//
//  Can be called multiple times with chunks of the transfer
//
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
	
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
    [self.responseData appendData:data];
    
}

//
// Called once at the end of the asynchrnous request to bango.
//
- (void)connectionDidFinishLoading:(NSURLConnection *)conn {
	NSString *response;
    
    
    response = [[NSString alloc]  initWithBytes:[self.responseData bytes]
                                         length:[self.responseData length]
                                       encoding: NSUTF8StringEncoding];
    
    BAMLog(@".");
    BAMLog(@".");
    BAMLog(@"%s", __PRETTY_FUNCTION__);
    
    [self handleTransmissionForResponse:response];
    
    // clean up memory
    [response release];
    
    
}

//
// Something bad happened before the response started
//
- (void)connection:(NSURLConnection *)connection 
  didFailWithError:(NSError *)error {
	
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
               BAMLog(@"error %@ occured whilst transmittingProfile: %@",
                   error, self.transmittingProfile);
    
    [PERSISTENCE updatePostedStatusOnTransmissionFail:self.transmittingProfile.id];
    
    self.transmittingProfile = nil;
    _transmitting = NO;
    self.responseData = nil;
    
    BAMLog(@"transmittingProfile data has been reset - aborting all transmissions");
    
    if (_delegate != nil && [_delegate respondsToSelector:@selector(transmitFailure)]) {
        [_delegate performSelector:@selector(transmitFailure)];
    }
    
}

#pragma mark -
#pragma mark NSXMLParser Methods
-(void)parseDidStartDocument:(NSXMLParser *)parser{
    BAMLog(@"XML Parse Started..");
    
}

-(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
    NSString *errorString = [NSString stringWithFormat:@"Error Parsing XML. Code: %ld", (long)[parseError code]];
    NSLog(@"Error Parsing XML: %@", errorString);
    errorParsing = YES;
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
    [elementValue appendString:string];
}

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
    currentElement = [elementName copy];
    elementValue = [[NSMutableString alloc] init];
    if ([elementName isEqualToString:@"idxml"]) {
        idxml = [[NSMutableDictionary alloc] init];
    }
}

-(void)parser:(NSXMLParser*)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
    if ([elementName isEqualToString:@"userId"]) {
        self.bangoUserId = elementValue;
        self.bangoUserId = [self.bangoUserId substringToIndex:[self.bangoUserId length] - 2];
        self.bangoUserId = [self.bangoUserId substringFromIndex:1];
        BAMLog(@"Bango User ID: %@", self.bangoUserId);
        [PERSISTENCE LogProfilePersistent:self.bangoUserId];
        [self transmitAll];
    }
}

#pragma mark -
#pragma mark Core Location Delegate Methods

// we implement this delegate method
- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
           fromLocation:(CLLocation *)oldLocation
{
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
    BAMLog(@"Location Updated: %@", [newLocation description]);
	
	// update the current profile with this location
	
	BAMLog(@"location updated");
	
	double locationLatitude = [BGOHelperUtils getRoundedLocation:newLocation.coordinate.latitude];
	double locationLongitude = [BGOHelperUtils getRoundedLocation:newLocation.coordinate.longitude];
	double locationAltitude = [BGOHelperUtils getRoundedLocation:newLocation.altitude];
	double locationAccuracy = [BGOHelperUtils getRoundedLocation:newLocation.horizontalAccuracy];
    
	// save the changes
	[PERSISTENCE updateProfileLocationWithLocationLatitude:locationLatitude
                                         locationLongitude:locationLongitude
                                          locationAltitude:locationAltitude
                                          locationAccuracy:locationAccuracy];
	
	BAMLog(@"new location lat:%f, long:%f alt:%f, accuracy:%f", locationLatitude,
           locationLongitude, locationAltitude, locationAccuracy);
	
	// we can now shut down the core location manager
	[self.locationManager stopUpdatingLocation];
}

- (void)locationManager:(CLLocationManager *)manager
       didFailWithError:(NSError *)error {
	BAMLog(@"> %s", __PRETTY_FUNCTION__);
	BAMLog(@"Error: %@", [error description]);
	// we can now shut down the core location manager
	[self.locationManager stopUpdatingLocation];
}

-(NSString *)connectionType {
    NSString *connectionType = nil;
	if ([[BGOReachability reachabilityForInternetConnection] currentReachabilityStatus] == kReachableViaWiFi) {
		connectionType = BGO_CONNECTION_TYPE_WIFI;
	} else if ([[BGOReachability reachabilityForInternetConnection] currentReachabilityStatus] == kReachableViaWWAN) {
		connectionType = BGO_CONNECTION_TYPE_OPERATOR;
	} else {
		connectionType = BGO_CONNECTION_TYPE_OTHER;
	}
    
    return connectionType;
}

-(void)setEventCount:(int)totalEventCount{
  
}



-(void)setDebugLog:(BOOL)debugLog {
    _debugLog = debugLog;
    [PERSISTENCE setDebugLog:debugLog];
}

-(BOOL)isDebugLog {
    return _debugLog;
}

- (int)postChunkSize {
    return PERSISTENCE.postChunkSize;
}

- (void)setPostChunkSize:(int) postChunkSize {
    PERSISTENCE.postChunkSize = postChunkSize;
}

#pragma mark -
#pragma mark Custom Debugger

// logs to debug
// based on method described by Agent M here http://borkware.com/rants/agentm/mlog/
-(void)debug:(char *)file
  lineNumber:(int)lineNumber
      format:(NSString *)format, ... {
	if (_debugLog) {
        va_list ap;
        NSString *print,*fileName;
        va_start(ap,format);
        fileName=[[NSString alloc] initWithBytes:file 
                                          length:strlen(file) 
                                        encoding:NSUTF8StringEncoding];
        print=[[NSString alloc] initWithFormat:format arguments:ap];
        va_end(ap);
        //NSLog handles synchronization issues
        NSLog(@"%s:%d %@", [[fileName lastPathComponent] UTF8String],
              lineNumber, print);
        [print release];
        [fileName release];
	}
}

@end